#region (c) 2014 THN Solutions LLC. - All rights reserved
/*
THN TELEPORT version 2

Copyright (c) 2014, THN Solutions LLC. ( www.ThnSolutions.com )
Author: Nguyễn, M. Hải                 ( www.Minh-Hai.com     )
All rights reserved.

This library is dual-licensed.
  + For commercial software, please obtain a commercial license from THN Solutions LLC.

  + For free software, this library is licensed under GPL version 3. A summary of GPLv3 is
    listed below. You should also find a copy of GPLv3 as file License_GPLv3.txt included
	with the source-code files.

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License version 3 as published by
    the Free Software Foundation  of the License.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/
#endregion

#region Using directives
using System;
using System.Collections;
using System.ComponentModel;

using Thn;
using Thn.Application.Configuration;

using Thn.Communication.Teleporting;
using Thn.Communication.Teleporting.Settings;

using System.Runtime.Remoting.Channels;
using System.Runtime.Remoting.Channels.Http;
using System.Runtime.Remoting.Channels.Tcp;
using System.Runtime.Remoting.Channels.Ipc;
using System.Runtime.Serialization.Formatters;
#endregion

namespace Thn.Communication.Teleporting.Connectors.Remoting
{
    /// <summary>
    /// An Client connector for Teleport using remoting
    /// </summary>
    public class ClientConnector : Component, ITeleportConnector
    {
        #region Properties

        #region Server
        private string mServer = "localhost";
        /// <summary>
        /// Gets/Sets Server
        /// </summary>
        [Savable]
        public string Server
        {
            get { return mServer; }
            set { mServer = value; }
        }
        #endregion

        #region Port
        private int mPort = 8080;
        /// <summary>
        /// Gets/Sets Port
        /// </summary>
        public int Port
        {
            get { return mPort; }
            set { mPort = value; }
        }
        #endregion

        #region Uri
        private string mUri = "TeleportService";
        /// <summary>
        /// Gets/Sets URI
        /// </summary>
        public string Uri
        {
            get { return mUri; }
            set
            {
                if (Uri != value)
                {
                    mUri = value;
                }
            }
        }
        #endregion

        #region Channel Type
        private ChannelType mChannelType = ChannelType.Tcp;
        /// <summary>
        /// Gets/Sets type of tramission protocol.
        /// <para>Default is <see cref="ChannelType.Tcp"/></para>
        /// </summary>
        public ChannelType ChannelType
        {
            get { return mChannelType; }
            set { mChannelType = value; }
        }
        #endregion

        #region Channel Name
        private string mChannelName = "TeleportChannel";
        /// <summary>
        /// Gets/Sets name of the channel when used in IPC.
        /// </summary>
        public string ChannelName
        {
            get { return mChannelName; }
            set { mChannelName = value; }
        }
        #endregion

        #region Formatter
        private FormatterType mFormatter = FormatterType.Soap;
        /// <summary>
        /// Gets/Sets type of format for serialiation
        /// </summary>
        public FormatterType Formatter
        {
            get { return mFormatter; }
            set { mFormatter = value; }
        }
        #endregion

        #endregion

        #region Get Connector
        private IServerConnector mConnector = null;
        /// <summary>
        /// Get Connector reveive request from server
        /// </summary>
        void GetConnector()
        {
            if (mConnector == null)
            {
                #region Register Channel

                IServerFormatterSinkProvider serverProvider = null;
                IClientFormatterSinkProvider clientProvider = null;

                #region Create Formatter Sink Provider
                //Use the default BINARY formatting
                if (mFormatter == FormatterType.Binary)
                {
                    serverProvider = new BinaryServerFormatterSinkProvider();
                    clientProvider = new BinaryClientFormatterSinkProvider();
                }
                //Use the default SOAP formatting
                else if (mFormatter == FormatterType.Soap)
                {
                    serverProvider = new SoapServerFormatterSinkProvider();
                    clientProvider = new SoapClientFormatterSinkProvider();
                }
                #endregion

                #region Common initialization
                //Common initialization for both SOAP and binary
                IDictionary props = new Hashtable();

                #endregion

                #region Create channel

                IChannel mChannel = null;
                if (mChannelType == ChannelType.Tcp)
                {
                    props["port"] = 0;//0: any port
                    props["typeFilterLevel"] = TypeFilterLevel.Full;
                    mChannel = new TcpChannel(props, clientProvider, serverProvider);
                }
                else if (mChannelType == ChannelType.Http)
                {
                    props["port"] = 0;//0: any port
                    props["typeFilterLevel"] = TypeFilterLevel.Full;
                    mChannel = new HttpChannel(props, clientProvider, serverProvider);
                }
                else if (mChannelType == ChannelType.Ipc)
                {
                    //props["portName"] = mUri;
                    //props["channelName"] = mUri;
                    //mChannel = new IpcChannel(props, clientProvider, serverProvider);
                    //string uri = string.Format("ipc://{0}/{1}", mChannelName, mUri);
                    string uri = string.Format("ipc://{1}", mChannelName, mUri);
                    mChannel = new IpcClientChannel(mUri, clientProvider);
                    //mChannel = new IpcClientChannel(mUri);
                }

                #endregion

                //Register
                ChannelServices.RegisterChannel(mChannel, false);

                #endregion

                mConnector = (IServerConnector)Activator.GetObject(
                        typeof(IServerConnector),
                        GetAddress());
            }
        }

        #region Get Address
        /// <summary>
        /// Get address from Server name, port, Uri, channel
        /// </summary>
        /// <returns></returns>
        string GetAddress()
        {
            //format: channel://server name:port number/Uri
            string result = string.Empty;
            if (mChannelType == ChannelType.Http)
                result = string.Format("http://{0}:{1}/{2}", mServer, mPort, mUri);
            else if (mChannelType == ChannelType.Tcp)
                result = string.Format("tcp://{0}:{1}/{2}", mServer, mPort, mUri);
            return result;
        }
        #endregion

        #endregion

        #region Apply Configuration
        /// <summary>
        /// Read parameters from configuration.
        /// </summary>
        void ApplyConfiguration(ConfigSection section)
        {
            //Server
            string tmp = section["Server"];
            if ((tmp != null) && (tmp != string.Empty)) mServer = tmp;

            //Channel Type
            tmp = section["ChannelType"];
            if ((tmp != null) && (tmp != string.Empty))
            {
                string ct = tmp.Trim().ToUpper();
                if (ct == "TCP") mChannelType = ChannelType.Tcp;
                else if (ct == "HTTP") mChannelType = ChannelType.Http;
                else if (ct == "IPC") mChannelType = ChannelType.Ipc;
            }

            //Channel Name
            tmp = section["ChannelName"];
            if ((tmp != null) && (tmp != string.Empty)) mChannelName = tmp;

            //Port
            tmp = section["Port"];
            if ((tmp != null) && (tmp != string.Empty))
            {
                #region Save Port
                Exception error = null;
                try
                {
                    mPort = int.Parse(tmp);
                }
                catch (Exception ex)
                {
                    error = ex;
                }
                #endregion
            }

            //Uri
            tmp = section["Uri"];
            if ((tmp != null) && (tmp != string.Empty)) mUri = tmp;

            //Formatter
            tmp = section["Formatter"];
            if ((tmp != null) && (tmp != string.Empty))
            {
                string fm = tmp.Trim().ToUpper();
                if (fm == "BINARY") mFormatter = FormatterType.Binary;
                else if (fm == "SOAP") mFormatter = FormatterType.Soap;
            }

            //Trace
            tmp = section["Trace"];
            if ((tmp != null) && (tmp != string.Empty))
            {
                tmp = tmp.Trim().ToUpper();
                if (tmp == "TRUE") TeleportSettings.Tracing = true;
            }
        }
        #endregion

        #region ITeleportConnector Members

        #region Teleporter
        private ITeleporter mTeleporter;
        /// <summary>
        /// Gets/Sets the teleporter this connector shall be connecting for.
        /// <para>When unassigned, the connector shall use the default teleporter.</para>
        /// </summary>
        public ITeleporter Teleporter
        {
            get
            {
                ITeleporter result = null;
                if (mTeleporter != null) result = mTeleporter;
                else result = Teleport.Teleporter;

                return result;
            }
            set { mTeleporter = value; }
        }
        #endregion

        #region Configuration
        private ConfigSection mConfiguration;
        /// <summary>
        /// Gets/Sets a custom configuration for initialization.
        /// </summary>
        public ConfigSection Configuration
        {
            get { return mConfiguration; }
            set
            {
                mConfiguration = value;
                if (mConfiguration != null) ApplyConfiguration(mConfiguration);
                else Thn.Exceptions.NullArgumentException.Publish("Configuration");
            }
        }
        #endregion

        #region Initialize
        /// <summary>
        /// Prepare this connector.
        /// </summary>
        public void Initialize()
        {
            //Get teleporter object
            Teleporter tp = (Teleporter)this.Teleporter;

            //Intercept send message event to process by sending the message to remoting
            tp.SendMessage += new TeleportRequestHandler(tp_SendMessage);
        }
        #endregion

        #endregion

        #region Teleport - Send message
        /// <summary>
        /// Intercept send message event to process by sending the message to remoting
        /// </summary>
        string tp_SendMessage(string gateway, string method, string encodedArgs)
        {

            string result = string.Empty;
            Exception error = null;
            try
            {
                //Get object from server
                GetConnector();

                //receive the message using remoting
                if (mConnector != null)
                    result = mConnector.TeleportRequest(gateway, method, encodedArgs);
            }
            catch (Exception ex)
            {
                error = ex;
            }
            //rethrow error
            if (error != null)
            {
                //ClientException.Publish_CannotConnectToServer(error);
                throw error;
            }
            //Return
            return result;
        }
        #endregion

        #region Constructors
        /// <summary>
        /// Default constructor
        /// </summary>
        public ClientConnector()
        { }
        #endregion
    }
}
